﻿using FastIM;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

public static class FastIMServerExtenssions
{
    static bool isUseWebSockets = false;

    /// <summary>
    /// 启用 ImServer 服务端
    /// </summary>
    /// <param name="app"></param>
    /// <param name="options"></param>
    /// <see cref="https://github.com/2881099/FreeIM"/>
    /// <seealso cref="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/websockets"/>
    /// <seealso cref="https://www.inte.net/news/265740.html"/>
    /// <seealso cref="https://www.cnblogs.com/dandan123/p/10059026.html"/>
    /// <seealso cref="https://vycc.cn/biancheng/2354131/"/>
    /// <seealso cref="https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/fundamentals/websockets/samples/6.x/WebSocketsSample/Controllers/WebSocketController.cs"/>
    /// <returns></returns>
    public static IApplicationBuilder UseFastIMServer(this IApplicationBuilder app, ImServerOptions options)
    {
        app.Map(options.PathMatch, appcur =>
        {
            if (isUseWebSockets == false)
            {
                isUseWebSockets = true;
                appcur.UseWebSockets();
            }
            var imserv = new ImServer(options);
            appcur.Use((ctx, next) => imserv.Acceptor(ctx, next));
        });
        Console.WriteLine("FastIMServer启动成功");
        return app;
    }
    /// <summary>
    /// 添加自定义消息处理器
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddCustomMsgHandlers(this IServiceCollection services)
    {
        foreach (var type in Assembly.GetEntryAssembly().ExportedTypes)
        {
            if (type.GetTypeInfo().BaseType == typeof(IMsgHandler))
            {
                MsgHandlerManager.Instance().Add((IMsgHandler)Activator.CreateInstance(type));
            }
        }
        return services;
    }
}

/// <summary>
/// im 核心类实现的配置所需
/// </summary>
public class ImServerOptions : ImClientOptions
{
    /// <summary>
    /// 设置服务名称，它应该是 servers 内的一个
    /// </summary>
    public string Server { get; set; }
}
public class ImServer : ImClient
{
    protected string _server { get; set; }
    protected ImSessionFactory wsfactory = ImSessionFactory.Instance();
    public ImServerOptions ServerOptions { get; set; }
    public ImServer(ImServerOptions options) : base(options)
    {
        ServerOptions = options;
        _server = options.Server;
        MsgHandlerManager.Instance().Init();//初始化消息处理器
        _pubsub.Subscribe($"{_redisPrefix}Server{_server}", RedisSubScribleMessage);//订阅服务消息，消息中转   
    }
    async Task Error(HttpContext context, int code, string msg)
    {
        context.Response.StatusCode = code; await context.Response.WriteAsync(msg); return;
        // throw new Exception("授权错误：用户需通过 ImHelper.PrevConnectServer 获得包含 token 的连接");
    }
    internal async Task Acceptor(HttpContext context, Func<Task> next)
    {
        if (!context.WebSockets.IsWebSocketRequest) { await Error(context, StatusCodes.Status400BadRequest, "不是WebSocket请求"); return; }
        //客户端登录token
        string token = context.Request.Query["token"];
        if (string.IsNullOrEmpty(token))
        {
            await Error(context, StatusCodes.Status401Unauthorized, "授权错误：用户需通过 ImHelper.PrevConnectServer 获得包含 token 的连接");return;
        }
        //客户端登录信息
        (string clientId, ClientMetaData clientMetaData) logininfo;
        try { logininfo = Helper.DecodeToken(token); }
        catch (Exception ex) { await Error(context, StatusCodes.Status401Unauthorized, $"授权错误：{ex.Message}"); return; }

        //var logininfo = JsonConvert.DeserializeObject<(string clientId, ClientMetaData clientMetaData)>(token_value);
        //客户端登录设备名称。用于一个设备端只能连接一个websocket
        var deviceid = logininfo.clientMetaData.DeviceName;
        //判断用户的某个客户端是否已经登录
        if (wsfactory.Exist(logininfo.clientId, deviceid))
        {
            await Error(context, StatusCodes.Status503ServiceUnavailable, "该设备已经连接websocket服务器，请先断开连接再登录"); return;
        }
        //获取WebSocket
        var socket = await context.WebSockets.AcceptWebSocketAsync();
        //创建客户端
        var customWebSocket = new ImSession(logininfo.clientId, socket, logininfo.clientMetaData);      
        //获取用户的WebSocket列表
        wsfactory.Add(logininfo.clientId, deviceid, customWebSocket);//一个用户可以有多个WebSocket,可以用来表示一个用户可以多端登录
        var logininfo_jsonstr = Helper.SerializeObject(logininfo);//登录信息json字符串
        using (var pipe = _redis.StartPipe())
        {
            pipe.HIncrBy($"{_redisPrefix}Online", logininfo.clientId.ToString(), 1);//客户端设备数量加1
            //pipe.Publish($"evt_{_redisPrefix}Online", token_value);//发布上线事件
            _pubsub.Publish($"evt_{_redisPrefix}Online", logininfo_jsonstr);//发布上线事件        
            pipe.EndPipe();
        }
        //监听消息
        await Listen(customWebSocket);
        //删除客户端设备
        wsfactory.Remove(logininfo.clientId, deviceid, out var oldcli);
        //判断客户端是否还有设备连接，没有则删除该客户端的存储数据包括结构
        if (wsfactory.Any(logininfo.clientId) == false) wsfactory.Remove(logininfo.clientId, out var oldwslist);// _clients.TryRemove(data.clientId, out var oldwslist);
        _redis.Eval($"if redis.call('HINCRBY', KEYS[1], '{logininfo.clientId}', '-1') <= 0 then redis.call('HDEL', KEYS[1], '{logininfo.clientId}') end return 1", new[] { $"{_redisPrefix}Online" });
        LeaveChan(logininfo.clientId, GetChanListByClientId(logininfo.clientId));
        _pubsub.Publish($"evt_{_redisPrefix}Offline", logininfo_jsonstr);
    }
    //监听客户端发送过来的消息
    private async Task Listen(ImSession customWebSocket)
    {
        WebSocket webSocket = customWebSocket.socket;
        var buffer = new byte[1024 * 4];
        try
        {
            //接收发送过来的消息，这个方法是阻塞的，如果没收到消息则一直阻塞
            WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            //将字节流循环读取到一个缓存区里，直至所有数据都被接收完
            //循环接收消息
            while (/*webSocket.State == WebSocketState.Open && wsfactory.ContainsKey(customWebSocket.clientId) && */!result.CloseStatus.HasValue)
            {
                //var incoming = await socket.ReceiveAsync(seg, CancellationToken.None);
                //var outgoing = new ArraySegment<byte>(buffer, 0, incoming.Count);
                //await wsmHandler.HandleMessage(result, buffer, userWebSocket, wsFactory);
                await ReceiveAsync(customWebSocket, result, buffer);
                buffer = new byte[1024 * 4];
                result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            }
            //socket.Abort();
            //关闭当前WebSocket连接
            await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
        }
        catch { }
    }
    private async Task ReceiveAsync(ImSession customWebSocket, WebSocketReceiveResult result, byte[] data)
    {
        //如果是纯文本消息
        //http://www.ruanyifeng.com/blog/2017/05/websocket.html
        if (result.MessageType == WebSocketMessageType.Text)
        {
            //因为缓冲区长度是固定的所以要获取实际长度
            string msg = Encoding.UTF8.GetString(data[..result.Count]).TrimEnd('\0');
            //if (string.IsNullOrEmpty(msg)) return;
            //接收的到消息转换成实体
            MsgTemplate msgTemplate = JsonConvert.DeserializeObject<MsgTemplate>(msg);
            //消息处理
            var handlers = MsgHandlerManager.Instance().Get();
            foreach (var handler in handlers)
            {
                if (handler.Accept(msgTemplate))
                {
                    await handler.Handle(msgTemplate, customWebSocket, this);
                }
            }
        }
        //https://www.saoniuhuo.com/question/detail-2260387.html
        else if (result.MessageType == WebSocketMessageType.Binary)
        {
            //await _webSocketHandler.OnDisconnected(socket);
            return;
        }
    }
    async void RedisSubScribleMessage(string chan, object msg)
    {
        try
        {
            ImSendEventArgs data = JsonConvert.DeserializeObject<ImSendEventArgs>(msg as string);
            //发送全员消息
            if (data != null && data.Channel == "all") { await SendLocalSeverAllMessage(data.SenderClientId, data.Message,data.MsgId); return; };
            //Console.WriteLine($"收到消息：{data.Message}" + (data.Receipt ? "【需回执】" : ""));
            // var outgoing = new ArraySegment<byte>(Encoding.UTF8.GetBytes(data.Message.ToString()));
            foreach (var clientId in data.ReceiveClientId)//循环所有接收者
            {
                //clientId接收者id不在线。可以存储到历史聊天服务器
                if (wsfactory.Get(clientId, out var devicelist) == false)
                {
                    //Console.WriteLine($"websocket{clientId} 离线了，{data.content}" + (data.receipt ? "【需回执】" : ""));
                    //发送者不是接收者本身，且需要回执 则给发送者返回一条接收者不在线的消息                   
                    if (data.SenderClientId != string.Empty && clientId != data.SenderClientId && data.Qos== QualityOfServiceLevel.AtMostOnce)
                        await SendMessage(data.Server, new[] { data.SenderClientId }, new
                        {
                            //data.Message,
                            ack = false,//确认标志
                            ackmsg = "用户不在线"
                        },MsgId:data.MsgId);
                    continue;
                }
                //发送消息
                ImSession[] sockarray = devicelist.Values.ToArray();
                //如果接收消息人是发送者，并且接收者只有1个以下，则不发送
                //只有接收者为多端时，才转发消息通知其他端
                //if (clientId == data.SenderClientId && sockarray.Length <= 1) continue; //注释掉表示自己能给自己发信息
                //发送socket
                await SendDeviceSocketsMessage(data.Server, data.SenderClientId, clientId, sockarray, data.Message, data.Qos, data.Channel, data.SenderMetaData,data.MsgId,data.Code,data.Type);
                //发送成功回执，非群聊消息       
                if (data.SenderClientId != string.Empty && clientId != data.SenderClientId && data.Qos == QualityOfServiceLevel.AtMostOnce)
                {
                    await SendMessage(data.Server, new[] { data.SenderClientId }, new
                    {
                        ack = true,//确认标志
                        ackmsg = "发送成功"
                    }, MsgId: data.MsgId);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"订阅方法出错了：{ex.Message}");
        }
    }
    /// <summary>
    /// 发送本im服务器节点的全员消息
    /// </summary>
    /// <param name="message"></param>
    private async Task SendLocalSeverAllMessage(string senderClientId, object message,string msgId=null)
    {
        var wf = ImSessionFactory.Instance();
        foreach (var device in wf.All().Values)
        {
            await SendDeviceSocketsMessage(null, senderClientId, null, device.Values, message, QualityOfServiceLevel.AtMostOnce, "all",  null, msgId,0,MsgType.PublishOne2All);
        }
    }
}

